package cl.parser.internal;

import java.util.ArrayList;
import java.util.List;

import cl.lexer.Token;
import cl.parser.IParseNode;
import cl.parser.ParseException;

public abstract class InternalNode implements IParseNode {
    protected List<IParseNode> children;
    
    public InternalNode() {
        children = new ArrayList<IParseNode>();
    }
    
    public void addChild(IParseNode child) throws ParseException {
        children.add(child);
    }
    
    public String treePrint() {
        return treePrintHelper("|", "----");
    }
    
    private String treePrintHelper(String prefix, String indent) {
        String name = getClass().getSimpleName();
        
        String res = prefix + name.substring(0, name.length() - 4) + "\n";
        
        
        for(IParseNode p : children) {
            if(p instanceof InternalNode) {
                res += ((InternalNode)p).treePrintHelper(prefix + indent, indent);
            } else {
                name = p.getClass().getSimpleName();
                res += prefix + indent + name.substring(0, name.length() - 4) + " (" + p.prettyPrint("") + ")\n";
            }
        }
        
        return res;
    }
    
    @Override
    public IParseNode gobble(int startIndex, List<Token> tokens) throws ParseException {
        int canGobble = 0;
        int index = startIndex;
        
        IParseNode current = null;
        
        List<IParseNode> allowed = getAllowed(current, children);
        
        boolean inRecurse = containsSelf(allowed);
        
        while(true) {
            canGobble = 0;
            
            for(IParseNode p : allowed) {
                canGobble = p.canGobble(index, tokens);
                if(canGobble > 0) {
                    current = p;
                    break;
                }
            }
            if(canGobble == 0) {
                break;
            }
            
            current.gobble(index, tokens);
            
            if(current instanceof IFat) {
                ((IFat) current).loseWeight();
            }
            
            index += canGobble;
            
            addChild(current);
            allowed = getAllowed(current, children);
            
            if(inRecurse && allowed.isEmpty()) {
                try {
                    current = getClass().newInstance();
                    ((InternalNode)current).children = children;
                    children = new ArrayList<IParseNode>();
                    
                    addChild(current);
                    allowed = getAllowed(current, children);
                    
                    inRecurse = false;
                } catch (InstantiationException e) {
                    throw new ParseException(e.getMessage());
                } catch (IllegalAccessException e) {
                    throw new ParseException(e.getMessage());
                }
            }
        }
        
        if(children.size() == 1 && getClass().isInstance(children.get(0))) {
            children = ((InternalNode)children.get(0)).children;
        }
        
        return this;
    }
    
    @Override
    public int canGobble(int startIndex, List<Token> tokens) {
        
        int counter = 0;
        int canGobble;
        
        IParseNode current = null;
        
        List<IParseNode> soFar = new ArrayList<IParseNode>();
        List<IParseNode> allowed = getAllowed(current, soFar);
        
        boolean canExit = isValid(current, soFar);
        boolean inRecurse = containsSelf(allowed);
        
        while(true) {
            canGobble = 0;
            
            for(IParseNode p : allowed) {
                canGobble = p.canGobble(startIndex + counter, tokens);
            
                if(canGobble > 0) {
                    current = p;
                    break;
                }
            }
            
            if(canGobble == 0) {
                return canExit? counter : 0;
            }
            
            counter += canGobble;
            soFar.add(current);
            
            allowed = getAllowed(current, soFar);
            canExit = isValid(current, soFar);
            
            if(inRecurse && canExit && allowed.isEmpty()) {
                try {
                    current = getClass().newInstance();
                    ((InternalNode)current).children = soFar;
                    soFar = new ArrayList<IParseNode>();
                    soFar.add(current);
                    
                    allowed = getAllowed(current, soFar);
                    canExit = isValid(current, soFar);
                    
                    inRecurse = false;
                } catch (InstantiationException e) {
                    e.printStackTrace();
                    return 0;
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    return 0;
                }
            }
        }
    }

    private boolean containsSelf(List<IParseNode> allowed) {
        for(int i = 0; i < allowed.size(); i++) {
            if(getClass().isInstance(allowed.get(i))) {
                allowed.remove(i);
                return true;
            }
        }
        return false;
    }

    protected abstract List<IParseNode> getAllowed(IParseNode last, List<IParseNode> soFar);
    
    protected abstract boolean isValid(IParseNode last, List<IParseNode> soFar);
}
